/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package it.polimi.games.epkb.manager;

import it.polimi.games.epkb.controller.ActivityJpaController;
import it.polimi.games.epkb.controller.ComponentJpaController;
import it.polimi.games.epkb.controller.DataFileJpaController;
import it.polimi.games.epkb.controller.FileUsageJpaController;
import it.polimi.games.epkb.controller.GpiKpiJpaController;
import it.polimi.games.epkb.controller.ServiceInstanceJpaController;
import it.polimi.games.epkb.controller.StoredValuesJpaController;
import it.polimi.games.epkb.controller.ThresholdsJpaController;
import it.polimi.games.epkb.controller.exceptions.NonexistentEntityException;
import it.polimi.games.epkb.entityClasses.Activity;
import it.polimi.games.epkb.entityClasses.ActivityData;
import it.polimi.games.epkb.entityClasses.Component;
import it.polimi.games.epkb.entityClasses.FileUsage;
import it.polimi.games.epkb.entityClasses.ServiceInstance;
import it.polimi.games.epkb.entityClasses.StoredValues;
import it.polimi.games.epkb.entityClasses.Thresholds;
import it.polimi.games.epkb.entityClasses.ThresholdsPK;
import it.polimi.games.epkb.entityClasses.VirtualMachineInst;
import it.polimi.games.epkb.entityClasses.VirtualMachineResources;
import java.io.Serializable;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;
import javax.persistence.Query;

/**
 *
 * @author PoliMI
 */
public class EPKBmanager implements Serializable {

    private EntityManagerFactory emf = null;
    StoredValuesJpaController svC = null;
    ComponentJpaController componentC = null;
    GpiKpiJpaController gpiKpiC = null;
    ThresholdsJpaController tc = null;
    ServiceInstanceJpaController siC = null;
    ActivityJpaController activityJpaController = null;
    FileUsageJpaController fuC = null;
    DataFileJpaController dataFileC = null;
    ActivityJpaController activityC = null;
            


    public EPKBmanager() {
        emf = Persistence.createEntityManagerFactory("GAMES-EPKBPU");
        svC = new StoredValuesJpaController(emf);
        tc = new ThresholdsJpaController(emf);

        siC = new ServiceInstanceJpaController(emf);
        activityJpaController = new ActivityJpaController(emf);

        componentC = new ComponentJpaController(emf);
        gpiKpiC = new GpiKpiJpaController(emf);

        fuC = new FileUsageJpaController(emf);
        dataFileC = new DataFileJpaController(emf);
        activityC = new ActivityJpaController(emf);


    }

    public EPKBmanager(String persistence) {
        emf = Persistence.createEntityManagerFactory(persistence);
        svC = new StoredValuesJpaController(emf);
        tc = new ThresholdsJpaController(emf);

        siC = new ServiceInstanceJpaController(emf);
        activityJpaController = new ActivityJpaController(emf);

        componentC = new ComponentJpaController(emf);
        gpiKpiC = new GpiKpiJpaController(emf);

        fuC = new FileUsageJpaController(emf);
        dataFileC = new DataFileJpaController(emf);
        activityC = new ActivityJpaController(emf);

    }

    public EntityManagerFactory getEmf() {
        return this.emf;
    }

    /** Inserts a new computed value for a gpi/kpi in the stored_values table. 
     *@return true if the new value is inserted
     */
    public boolean insertGPIKPIValue(String ComponentID, String gpiKpiId, int ContextID, BigDecimal value, boolean red, boolean yellow) {
        StoredValues sv = new StoredValues();

        sv.setIdComponent(componentC.findComponent(ComponentID));
        sv.setContextID(ContextID);
        sv.setIdGPIKPI(gpiKpiC.findGpiKpi(gpiKpiId));
        //sv.setId(5);
        sv.setRed(red);
        sv.setDefinitionTime(new Timestamp(System.currentTimeMillis()));
        sv.setComputedValue(value);
        sv.setYellow(yellow);

        boolean result = true;

        svC.create(sv);

        return result;
    }

    /** Inserts a new computed value for a gpi/kpi in the stored_values table. The value is a random value between minValue and maxValue.
     * If minValue and maxValue are null, a random value is generated in a free range
     *@param ComponentID ID of the component
     *@param gpiKpiId ID of the gpi/kpi
     *@param minValue min value
     *@param maxValue max value
     *@return true if the new value is inserted
     */
    public boolean insertRandomGPIKPIValue(String ComponentID, String gpiKpiId, BigDecimal minValue, BigDecimal maxValue) {

        StoredValuesJpaController svC = new StoredValuesJpaController(emf);
        StoredValues sv = new StoredValues();

        ComponentJpaController componentC = new ComponentJpaController(emf);
        GpiKpiJpaController gpiKpiC = new GpiKpiJpaController(emf);

        BigDecimal value = null;

        if ((minValue != null) & (minValue != null)) {
            value = new BigDecimal(minValue.doubleValue() + (Math.random() * (maxValue.doubleValue() - minValue.doubleValue() + 1)));
        } else if ((minValue == null) & (minValue != null)) {
            value = new BigDecimal((Math.random() * (maxValue.doubleValue() + 1)));
        } else if ((minValue != null) & (minValue == null)) {
            value = new BigDecimal(minValue.doubleValue() + (Math.random() * (Math.abs(minValue.doubleValue() + 51))));
        }

        sv.setIdComponent(componentC.findComponent(ComponentID));
        sv.setContextID(1 + (int) (Math.random() * 300));
        sv.setIdGPIKPI(gpiKpiC.findGpiKpi(gpiKpiId));
        sv.setRed(Math.random() > 0.5 ? true : false);
        sv.setComputedValue(value);
        sv.setYellow(Math.random() > 0.5 ? true : false);

        boolean result = true;

        svC.create(sv);
        return result;
    }

    /** Returns TRUE if the value is outside the range of emergency thresholds, FALSE otherwise
     *@param ComponentID ID of the component
     *@param gpiKpiId ID of the gpi/kpi
     *@param value value to be checked
     *@return TRUE if the value is outside the range of emergency thresholds, FALSE otherwise
     */
    public boolean isRed(String componentID, String gpiKpiID, BigDecimal value) {
        boolean res;
        ThresholdsPK tPK = new ThresholdsPK();
        tPK.setIdComponent(componentID);
        tPK.setIdGPIKPI(gpiKpiID);
        Thresholds t = tc.findThresholds(tPK);

        if ((value.compareTo(t.getEmergencyRangeHigh()) == 1) || (value.compareTo(t.getEmergencyRangeLow()) == -1)) {
            res = true;
        } else {
            res = false;
        }
        return res;
    }

    /** Returns TRUE if the value is outside the range of normal thresholds but in the emergency thresholds, FALSE otherwise
     *@param ComponentID ID of the component
     *@param gpiKpiId ID of the gpi/kpi
     *@param value value to be checked
     *@return TRUE if the value is outside the range of normal thresholds but in the emergency thresholds, FALSE otherwise
     */
    public boolean isYellow(String componentID, String gpiKpiID, BigDecimal value) {
        boolean res;
        ThresholdsPK tPK = new ThresholdsPK();
        tPK.setIdComponent(componentID);
        tPK.setIdGPIKPI(gpiKpiID);
        Thresholds t = tc.findThresholds(tPK);

        if (((value.compareTo(t.getNormalRangeHigh()) == 1) & (value.compareTo(t.getEmergencyRangeHigh()) == -1)) || ((value.compareTo(t.getNormalRangeLow()) == -1) & (value.compareTo(t.getEmergencyRangeLow()) == 1))) {
            res = true;
        } else {
            res = false;
        }
        return res;
    }

    /** Sets a thresholds set of value for a componentID and a gpiKpiID
     */
    public void setThreshold(String componentID, String gpiKpiID, BigDecimal emergencyLow, BigDecimal normalLow, BigDecimal normalHigh, BigDecimal emergencyHigh) {

        ThresholdsPK tPK = new ThresholdsPK();
        tPK.setIdComponent(componentID);
        tPK.setIdGPIKPI(gpiKpiID);
        Thresholds t = tc.findThresholds(tPK);

        if (emergencyLow != null) {
            t.setEmergencyRangeLow(emergencyLow);
        }


        if (emergencyHigh != null) {
            t.setEmergencyRangeHigh(emergencyHigh);
        }

        if (normalLow != null) {
            t.setNormalRangeLow(normalLow);
        }

        if (normalHigh != null) {
            t.setNormalRangeHigh(normalHigh);
        }


        try {
            tc.edit(t);
        } catch (NonexistentEntityException ex) {
            Logger.getLogger(EPKBmanager.class.getName()).log(Level.SEVERE, null, ex);
        } catch (Exception ex) {
            Logger.getLogger(EPKBmanager.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    /** Returns the number of allocated CPU cores for a service instance
     *@param serviceInstanceID id of the service instance
     *@return int number of the allocated CPU cores
     */
    public int getAllocatedCPUCores(String serviceInstanceID) {
        int cores = 0;

        ServiceInstance si = siC.findServiceInstance(serviceInstanceID);

        VirtualMachineInst vmi = si.getIdVMInstance();

        Collection<VirtualMachineResources> c = vmi.getVirtualMachineResourcesCollection();

        for (VirtualMachineResources v: c) {

            if (v.getComponent().getCategory().equalsIgnoreCase("CPUCore")) {
                cores++;
            }
        }
        return cores;
    }

    /** Returns the number of allocated disk arrays for a service instance
     *@param serviceInstanceID id of the service instance
     *@return double number of the allocated disk arrays
     */
    public double getAllocatedDiskArray(String serviceInstanceID) {
        double diskAllocation = 0;

        ServiceInstance si = siC.findServiceInstance(serviceInstanceID);

        VirtualMachineInst vmi = si.getIdVMInstance();
        Collection<VirtualMachineResources> c = vmi.getVirtualMachineResourcesCollection();

        for (VirtualMachineResources v: c){
            if (v.getComponent().getCategory().equalsIgnoreCase("DiskArray")) {
                diskAllocation += v.getAllocated().doubleValue();
            }
        }
        return diskAllocation;
    }

    /** Returns the quantity of allocated memoriy for a service instance
     *@param serviceInstanceID id of the service instance
     *@return double number of the allocated memory quantity
     */
    public double getAllocatedMemory(String serviceInstanceID) {
        double memoryAllocation = 0;

        //ServiceInstanceJpaController siC = new ServiceInstanceJpaController(emf);
        ServiceInstance si = siC.findServiceInstance(serviceInstanceID);

        VirtualMachineInst vmi = si.getIdVMInstance();
        Collection<VirtualMachineResources> c = vmi.getVirtualMachineResourcesCollection();

        for (VirtualMachineResources v: c) {
            if (v.getComponent().getCategory().equalsIgnoreCase("Memory")) {
                memoryAllocation += v.getAllocated().doubleValue();
            }
        }
        return memoryAllocation;
    }


    /** Returns an ArrayList containing the list of stored values of a given gpi/kpi
     *@param gpiKpiId id of the gpi/kpi
     *@return arrayList containing the list of stored values 
     */
   /* public ArrayList<StoredValues> getLastValue(String gpiKpiId) {

        ArrayList<StoredValues> records = new ArrayList<StoredValues>();
        //StoredValuesJpaController svC = new StoredValuesJpaController(emf);

        //GpiKpiJpaController gpiKpiC = new GpiKpiJpaController(emf);

        Query q1 = svC.getEntityManager().createQuery("SELECT DISTINCT s.idComponent FROM StoredValues s WHERE s.idGPIKPI=?1");
        q1.setParameter(1, gpiKpiC.findGpiKpi(gpiKpiId));

        List<Component> l = q1.getResultList();

        for (Component c : l) {
            records.add(getLastValue(c.getId(), gpiKpiId));
        }
        return records;

    }*/

    /** Returns an ArrayList containing the list of files used by an activity
     *@param activityID id of the activity
     *@return arrayList containing the list of files used by an activity
     */
    public ArrayList<String> getUsedFiles(String activityID) {


        Activity activity = activityJpaController.findActivity(activityID);
        Collection<ActivityData> p = activity.getActivityDataCollection();
        ArrayList<String> fileName = new ArrayList(p.size());
        for (ActivityData ad : p) {
            fileName.add(ad.getDataFile().getNameAndPath());
        }
        return fileName;
    }

    /** Inserts in the EPKB information about the number of read and write operations. 
     * New records are added to the existing one.
     *@return true if the insert operation is successful
     */
    public boolean insertFileUsage(String fileName, String activityID, long numberRead, long numberWrite) {
        //FileUsageJpaController fuC = new FileUsageJpaController(emf);
        FileUsage fu = new FileUsage();

        //DataFileJpaController dataFileC = new DataFileJpaController(emf);
        //ActivityJpaController activityC = new ActivityJpaController(emf);

        Query q2 = dataFileC.getEntityManager().createQuery("SELECT DISTINCT f.id FROM DataFile f , ActivityData a WHERE (a.activityDataPK.idDataFile=f.id AND a.activityDataPK.idActivity=?1 AND f.nameAndPath=?2)");
        q2.setParameter(1, activityID);
        q2.setParameter(2, fileName);

        List<String> l = q2.getResultList();
        String dataFileID = null;
        if (!l.isEmpty()) {
            dataFileID = l.get(0);
        }

        boolean result = false;

        if (dataFileID != null) {
            fu.setIdActivity(activityC.findActivity(activityID));
            //fu.setIdDataFile(dataFileC.findDataFile(dataFileID));
            fu.setNumberRead(new BigInteger(((Long) numberRead).toString()));
            fu.setNumberWrite(new BigInteger(((Long) numberWrite).toString()));
            fu.setDefinitionTime(new Timestamp(System.currentTimeMillis()));

            result = true;
            fuC.create(fu);
        }
        return result;
    }

    /** Inserts in the EPKB information about the number of read and write operations. 
     * New values are added to the existing data.
     * If other records with the same dataFileId and activityId are stored in the epkb, 
     * they are all removed and a unique record is stored, with the sum of all
     * the previous read and write operations
     *@return true if the insert operation is successful
     */
    public boolean insertAggregatedFileUsage(String fileName, String activityID, long numberRead, long numberWrite) {
        //FileUsageJpaController fuC = new FileUsageJpaController(emf);
        FileUsage fu = new FileUsage();

        //ataFileJpaController dataFileC = new DataFileJpaController(emf);
        //ActivityJpaController activityC = new ActivityJpaController(emf);

        Query q2 = dataFileC.getEntityManager().createQuery("SELECT DISTINCT f.id FROM DataFile f , ActivityData a WHERE (a.activityDataPK.idDataFile=f.id AND a.activityDataPK.idActivity=?1 AND f.nameAndPath=?2)");
        q2.setParameter(1, activityID);
        q2.setParameter(2, fileName);

        List<String> l = q2.getResultList();
        String dataFileID = null;
        if (!l.isEmpty()) {
            dataFileID = l.get(0);
        }

        boolean result = false;

        if (dataFileID != null) {
            //  Retry the existing records
            BigInteger sumRead = new BigInteger("0");
            BigInteger sumWrite = new BigInteger("0");

            EntityManager emD = emf.createEntityManager();
            try {
                EntityTransaction entr = emD.getTransaction();
                entr.begin();

                Query q1 = emD.createQuery("SELECT f FROM FileUsage f  WHERE (f.idActivity.id=?1 AND f.idDataFile.id=?2)");
                q1.setParameter(1, activityID);
                q1.setParameter(2, dataFileID);
                List<FileUsage> fileUsageList = q1.getResultList();

                //  Sum the existing values for read and write

                for (FileUsage ful : fileUsageList) {
                    sumRead = sumRead.add(ful.getNumberRead());
                    sumWrite = sumWrite.add(ful.getNumberWrite());
                }

                // Delete the existing records
                Query qDel = emD.createQuery("DELETE FROM FileUsage f  WHERE (f.idActivity.id=?1 AND f.idDataFile.id=?2)");
                qDel.setParameter(1, activityID);
                qDel.setParameter(2, dataFileID);
                qDel.executeUpdate();

                entr.commit();
            } finally {
            }

            fu.setIdActivity(activityC.findActivity(activityID));
            //fu.setIdDataFile(dataFileC.findDataFile(dataFileID));
            fu.setNumberRead(sumRead.add(new BigInteger(((Long) numberRead).toString())));
            fu.setNumberWrite(sumWrite.add(new BigInteger(((Long) numberWrite).toString())));
            fu.setDefinitionTime(new Timestamp(System.currentTimeMillis()));

            result = true;
            fuC.create(fu);
        }
        return result;
    }

    /** Returns the stored value of a gpi/kpi indicator, and regarding on a given component, 
     * immediately before a given timestamp. 
    
     *@return a StoredValues object; a NULL value is returned if no data are found
     */
    public StoredValues getIndicator(String gpiKpiId, String componentId, Date timestamp) {
        StoredValues sv = null;

        //StoredValuesJpaController svC = new StoredValuesJpaController(emf);
        //GpiKpiJpaController gpiKpiC = new GpiKpiJpaController(emf);
        //ComponentJpaController cC = new ComponentJpaController(emf);

        try {
            Query q1 = svC.getEntityManager().createQuery("SELECT MAX(s.definitionTime) FROM StoredValues s WHERE (s.idGPIKPI=?1) AND (s.idComponent=?2) AND (s.definitionTime<=?3)");
            q1.setParameter(1, gpiKpiC.findGpiKpi(gpiKpiId));
            q1.setParameter(2, componentC.findComponent(componentId));
            q1.setParameter(3, timestamp);

            List<Date> dtl = q1.getResultList();
            Date dt = dtl.get(0);
            
            Query q2 = svC.getEntityManager().createQuery("SELECT s FROM StoredValues s WHERE s.idGPIKPI=?1 AND s.idComponent=?2 AND s.definitionTime=?3");
            q2.setParameter(1, gpiKpiC.findGpiKpi(gpiKpiId));
            q2.setParameter(2, componentC.findComponent(componentId));
            q2.setParameter(3, dt);

            List <StoredValues> svl =  q2.getResultList();
            if (svl.size() > 0)
                sv = svl.get(0);
            
        } catch (javax.persistence.NoResultException ee) {
            sv = null;
        }

        return sv;

    }
}
